/* 
封装vuex组合式api：useMapState, useMapMutations, useMapGetters, useMapActions, useComputed 方便vuex 批量引入
*/
import { useStore } from 'vuex'
import { computed} from 'vue'

function isObject(obj) {
    return obj !== null && typeof obj === 'object'
}

function normalizeMap(map) {
    if (!isValidMap(map)) {
        return []
    }
    return Array.isArray(map)
        ? map.map(function (key) { return ({ key: key, val: key }); })
        : Object.keys(map).map(function (key) { return ({ key: key, val: map[key] }); })
}

function isValidMap(map) {
    return Array.isArray(map) || isObject(map)
}

function normalizeNamespace(fn) {
    return function (namespace, map) {
        if (typeof namespace !== 'string') {
            map = namespace;
            namespace = '';
        } else if (namespace.charAt(namespace.length - 1) !== '/') {
            namespace += '/';
        }
        return fn(namespace, map)
    }
}

function getModuleByNamespace(store, helper, namespace) {
    var module = store._modulesNamespaceMap[namespace];
    if (!module) {
        console.error(("[vuex] module namespace not found in " + helper + "(): " + namespace));
    }
    return module
}


const useMapState = normalizeNamespace(function (namespace, states) {
    var res = {};
    if (!isValidMap(states)) {
        console.error('[vuex] mapState: mapper parameter must be either an Array or an Object');
    }
    var store = useStore()
    normalizeMap(states).forEach(function (ref) {
        var key = ref.key;
        var val = ref.val;

        res[key] = function mappedState() {
            var state = store.state;
            var getters = store.getters;
            if (namespace) {
                var module = getModuleByNamespace(store, 'mapState', namespace);
                if (!module) {
                    return
                }
                state = module.context.state;
                getters = module.context.getters;
            }
            return typeof val === 'function'
                ? val.call(this, state, getters)
                : state[val]
        };
        // mark vuex getter for devtools
        res[key].vuex = true;
    });
    return res
});

/* Transfrom an useMapState or an useMapGetters to an computed object*/
const useComputed = function(obj){
    if (!isObject(obj)) {
        console.error('[vuex] useComputed: obj parameter must be either an useMapState or an useMapGetters');
    }
    return Object.entries(obj).reduce((obj, item)=>{
        obj[item[0]] = computed(item[1])
        return obj
    }, {})
}


/**
 * Reduce the code which written in Vue.js for committing the mutation
 * @param {String} [namespace] - Module's namespace
 * @param {Object|Array} mutations # Object's item can be a function which accept `commit` function as the first param, it can accept anthor params. You can commit mutation and do any other things in this function. specially, You need to pass anthor params from the mapped function.
 * @return {Object}
 */
const useMapMutations = normalizeNamespace(function (namespace, mutations) {
    var res = {};
    if (!isValidMap(mutations)) {
        console.error('[vuex] mapMutations: mapper parameter must be either an Array or an Object');
    }
    var store = useStore()
    normalizeMap(mutations).forEach(function (ref) {
        var key = ref.key;
        var val = ref.val;

        res[key] = function mappedMutation() {
            var args = [], len = arguments.length;
            while (len--) args[len] = arguments[len];

            // Get the commit method from store
            var commit = store.commit;
            if (namespace) {
                var module = getModuleByNamespace(store, 'mapMutations', namespace);
                if (!module) {
                    return
                }
                commit = module.context.commit;
            }
            return typeof val === 'function'
                ? val.apply(this, [commit].concat(args))
                : commit.apply(store, [val].concat(args))
        };
    });
    return res
});


/**
 * Reduce the code which written in Vue.js for getting the getters
 * @param {String} [namespace] - Module's namespace
 * @param {Object|Array} getters
 * @return {Object}
 */
const useMapGetters = normalizeNamespace(function (namespace, getters) {
    var res = {};
    if (!isValidMap(getters)) {
        console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object');
    }
    var store = useStore()
    normalizeMap(getters).forEach(function (ref) {
        var key = ref.key;
        var val = ref.val;

        // The namespace has been mutated by normalizeNamespace
        val = namespace + val;
        res[key] = function mappedGetter() {
            if (namespace && !getModuleByNamespace(store, 'mapGetters', namespace)) {
                return
            }
            if (!(val in store.getters)) {
                console.error(("[vuex] unknown getter: " + val));
                return
            }
            return store.getters[val]
        };
        // mark vuex getter for devtools
        res[key].vuex = true;
    });
    return res
});

/**
* Reduce the code which written in Vue.js for dispatch the action
* @param {String} [namespace] - Module's namespace
* @param {Object|Array} actions # Object's item can be a function which accept `dispatch` function as the first param, it can accept anthor params. You can dispatch action and do any other things in this function. specially, You need to pass anthor params from the mapped function.
* @return {Object}
*/
const useMapActions = normalizeNamespace(function (namespace, actions) {
    var res = {};
    if (!isValidMap(actions)) {
        console.error('[vuex] mapActions: mapper parameter must be either an Array or an Object');
    }
    var store = useStore()
    normalizeMap(actions).forEach(function (ref) {
        var key = ref.key;
        var val = ref.val;

        res[key] = function mappedAction() {
            var args = [], len = arguments.length;
            while (len--) args[len] = arguments[len];

            // get dispatch function from store
            var dispatch = store.dispatch;
            if (namespace) {
                var module = getModuleByNamespace(store, 'mapActions', namespace);
                if (!module) {
                    return
                }
                dispatch = module.context.dispatch;
            }
            return typeof val === 'function'
                ? val.apply(this, [dispatch].concat(args))
                : dispatch.apply(store, [val].concat(args))
        };
    });
    return res
});


export {
    useMapState,
    useMapMutations,
    useMapGetters,
    useMapActions,
    useComputed
}